子域名在asp.net core的主机上出现404错误

问题描述 投票:0回答:1

你们好,我想在我的网站上做动态子域名,像这样的。

  • user1.domain.com ---> user1的数据。
  • user2.domain.com ---> user2的数据。

视图相同,但取决于用户{0}。

它在本地工作正常,但当我尝试在主机上上传项目时总是得到404 not found。

enter image description here

Setup.cs

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }
    public static Microsoft.AspNetCore.DataProtection.IDataProtectionBuilder dataProtectionBuilder;

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<CookiePolicyOptions>(options =>
        {
            options.CheckConsentNeeded = context => false;
            options.MinimumSameSitePolicy = SameSiteMode.None;
        });

        services.ConfigureApplicationCookie(options =>
        {
            options.Cookie.Name = ".AspNetCore.Identity.Application";
            options.Cookie.SameSite = SameSiteMode.Strict;
            options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
        });                

        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(
                Configuration.GetConnectionString("MyConnection")));

        services.AddDefaultIdentity<ExtendIdentityUser>(options =>
        {
            options.Password.RequiredLength = 8;
            options.Password.RequireUppercase = false;
            options.Password.RequireNonAlphanumeric = false;
            options.Password.RequiredUniqueChars = 0;
            options.Password.RequireLowercase = false;
        })
        .AddEntityFrameworkStores<ApplicationDbContext>();

        services.AddScoped<ILoggingExceptionRepository, LoggingExceptionRepository>();
        services.AddScoped<IUserClaimsPrincipalFactory<ExtendIdentityUser>, CustomClaimsPrincipalFactory>();

        services.AddScoped<MyCustomRouter>();

        services.AddSubdomains();

        services.AddMvc(opts =>
        {
            opts.ModelBinderProviders.Insert(0, new FromHostBinderProvider());
        }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.UseAuthentication();
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            app.UseStatusCodePagesWithRedirects("/Error/{0}");
        }
        app.UseStaticFiles();
        app.UseCookiePolicy();
        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

FromHostBinderProvider.cs这个类从URL中获取子域名。

internal class FromHostAttribute : Attribute, IBindingSourceMetadata
{
    public static readonly BindingSource Instance = new BindingSource("FromHostBindingSource", "From Host Binding Source", true, true);
    public BindingSource BindingSource { get { return FromHostAttribute.Instance; } }
}

public class MyFromHostModelBinder : IModelBinder
{
    private readonly string _domainWithPort;

    public MyFromHostModelBinder()
    {
        this._domainWithPort = "domain.com";  // in real project, use by Configuration/Options
    }

    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var req = bindingContext.HttpContext.Request;
        var host = req.Host.Value;
        var name = bindingContext.FieldName;
        var userStr = req.Host.Value.Substring(0, host.Length - this._domainWithPort.Length);
        if (string.IsNullOrEmpty(userStr))
        {
            bindingContext.ModelState.AddModelError(name, $"cannot get {name} from Host Domain");
        }
        else
        {
            var result = Convert.ChangeType(userStr, bindingContext.ModelType);
            bindingContext.Result = ModelBindingResult.Success(result);
        }
        return Task.CompletedTask;
    }

}

public class FromHostBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null) { throw new ArgumentNullException(nameof(context)); }
        var has = context.BindingInfo?.BindingSource == FromHostAttribute.Instance;
        if (has)
        {
            return new BinderTypeModelBinder(typeof(MyFromHostModelBinder));
        }
        return null;
    }
}

主控器.cs

    public IActionResult UserPage([FromHost] string pageId, int? currentpage)
    {
        //[FromHost] this custom attribute to get sub-domain name from url 
        pageId = pageId.Split('.')[0];

        //view data or any business code on depend on sub-domain
        return View(model);
    }

经过大量的搜索,我发现了这个想法,但我不知道如何在我的代码中使用它们... ...

第一个想法。

这个RouteAsync方法在我这里从来没有被执行过

public class MyCustomRouter : MvcRouteHandler, IRouter
{
    private IActionContextAccessor _actionContextAccessor;
    private IActionInvokerFactory _actionInvokerFactory;
    private IActionSelector _actionSelector;
    private ILogger _logger;
    private DiagnosticSource _diagnosticSource;


    public MyCustomRouter(
        IActionInvokerFactory actionInvokerFactory,
        IActionSelector actionSelector,
        DiagnosticSource diagnosticSource,
        ILoggerFactory loggerFactory)
        : this(actionInvokerFactory, actionSelector, diagnosticSource, loggerFactory, actionContextAccessor: null)
    {
    }

    public MyCustomRouter(IActionInvokerFactory actionInvokerFactory, IActionSelector actionSelector, DiagnosticSource diagnosticSource,
        ILoggerFactory loggerFactory, IActionContextAccessor actionContextAccessor)
        : base(actionInvokerFactory, actionSelector, diagnosticSource,
        loggerFactory, actionContextAccessor)
    {
        _actionContextAccessor = actionContextAccessor;
        _actionInvokerFactory = actionInvokerFactory;
        _actionSelector = actionSelector;
        _diagnosticSource = diagnosticSource;
        _logger = loggerFactory.CreateLogger<MvcRouteHandler>();
    }

    public new Task RouteAsync(RouteContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        // *****
        // ⚠️ This is the important part! ⚠️
        // *****
        string Host = context.HttpContext.Request.Host.Host;
        if (Host == "localhost") // Change this the to your usual host
        {
            // Do nothing, normal routing
        }
        else
        {
            // You can do pretty much anything here, but I chose to switch
            // to a different controller. ✅
            context.RouteData.Values["controller"] = "Home";
            context.RouteData.Values.Add("Host", Host); // Add a variable for fun
        }

        // All the next code is copied from base class
        var candidates = _actionSelector.SelectCandidates(context);
        if (candidates == null || candidates.Count == 0)
        {
            return Task.CompletedTask;
        }

        var actionDescriptor = _actionSelector.SelectBestCandidate(context, candidates);
        if (actionDescriptor == null)
        {
            return Task.CompletedTask;
        }

        context.Handler = (c) =>
        {
            var routeData = c.GetRouteData();

            var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor);
            if (_actionContextAccessor != null)
            {
                _actionContextAccessor.ActionContext = actionContext;
            }

            var invoker = _actionInvokerFactory.CreateInvoker(actionContext);
            if (invoker == null)
            {
                throw new InvalidOperationException();
            }

            return invoker.InvokeAsync();
        };

        return Task.CompletedTask;
    }
}

第二个想法。

    internal enum RouteSubDomainBehavior { Redirect, Rewrite, }
internal class RouteSubDomainRule : IRule
{
    private readonly string _domainWithPort;
    private readonly RouteSubDomainBehavior _behavior;

    public RouteSubDomainRule(string domain, RouteSubDomainBehavior behavior)
    {
        this._domainWithPort = domain;
        this._behavior = behavior;
    }

    // custom this method according to your needs
    protected bool ShouldRewrite(RewriteContext context)
    {
        var req = context.HttpContext.Request;

        // only rewrite the url when it ends with target doamin
        if (!req.Host.Value.EndsWith(this._domainWithPort, StringComparison.OrdinalIgnoreCase))
            return false;

        // if already rewrite, skip
        if (req.Host.Value.Length == this._domainWithPort.Length)
            return false;

        // ... add other condition to make sure only rewrite for the routes you wish, for example, 
               skip the Hub
        return true;
    }

    public void ApplyRule(RewriteContext context)
    {
        if (!this.ShouldRewrite(context))
        {
            context.Result = RuleResult.ContinueRules;
            return;
        }
        var req = context.HttpContext.Request;
        if (this._behavior == RouteSubDomainBehavior.Redirect)
        {
            var newUrl = UriHelper.BuildAbsolute(req.Scheme, new HostString(this._domainWithPort), req.PathBase, req.Path, req.QueryString);
            var resp = context.HttpContext.Response;
            context.Logger.LogInformation($"redirect {req.Scheme}://{req.Host}{req.Path}?{req.QueryString} to {newUrl}");
            resp.StatusCode = 301;
            resp.Headers[HeaderNames.Location] = newUrl;
            context.Result = RuleResult.EndResponse;
        }
        else if (this._behavior == RouteSubDomainBehavior.Rewrite)
        {
            var host = req.Host.Value;
            var userStr = req.Host.Value.Substring(0, host.Length - this._domainWithPort.Length - 1);
            req.Host = new HostString(this._domainWithPort);
            var oldPath = req.Path;
            req.Path = $"/{userStr}/{req.Host}{oldPath}";
            context.Logger.LogInformation($"rewrite {oldPath} as {req.Path}");
            context.Result = RuleResult.SkipRemainingRules;
        }
        else
        {
            throw new Exception($"unknow SubDomainBehavoir={this._behavior}");
        }
    }
}

请我需要帮助解决这个问题,使用第一个想法或第二个想法或其他东西.记住这个问题只在主机上。它的工作正常的本地主机。

asp.net-core .net-core asp.net-core-mvc asp.net-core-2.2
1个回答
1
投票

我想你想做的是多租户。你会发现很多资源与该术语,这里有一个很好的开始。

https:/stackify.comwriting-multitenant-asp-net-core-applicationsamp。

© www.soinside.com 2019 - 2024. All rights reserved.